package com.zjl.redis.第18章_五大数据类型和源码解析;

/**
 * @see com.zjl.redis.第02章_十大数据类型.B3_Hash
 *
 * hash-max-ziplist-entries 512
 * hash-max-ziplist-value 64
 *
 * redis 7 中新有的
 * hash-max-listpack-entries 512
 * hash-max-listpack-value 64
 *
 * 在 redis 7 中
 * Hash类型键的字段个数小于 hash-max-ziplist-entries
 *      并且每个字段名和字段值的长度小于hash-max-ziplist-value 时,
 *
 * Redis才会使用OBJ_ENCODING_ZIPLIST来存储该键，
 *      前述条件任意一个不满足则会转换为OBJ_ENCODING_HT的编码方式 hashtable
 *
 * ***********************************************************************
 * Ziplist 压缩列表是一种紧凑编码格式，总体思想是多花时间来换取节约空间，
 *      即以部分读写性能为代价，来换取极高的内存空间利用率，
 *      因此只会用于字段个数少，且字段值也较小的场景。
 *      压缩列表内存利用率极高的原因与其连续内存的特性是分不开的。
 *
 * 当一个hash对象只包含少量键值对且每个键值对的键和值要么就是小整数要么就是长度比较短的字符串，
 * 那么它用ziplist作为底层实现
 *
 * ziplist 结构：
 *  ————————————————————————————————————————————————————————————————————————
 *  | zlbytes | zltail | zllen  |  entry1 | entry2 | ... | entryN | zlend  |
 *  ————————————————————————————————————————————————————————————————————————
 *  zlbytes： 类型-uint32_t  长度 4字节
 *      记录整个压缩列表占用的内存字节数:在对压缩列表进行内存重分配， 或者计算zlend的位置时使用
 *  zltail：  类型-uint32_t   长度 4字节
 *      记录压缩列表表尾节点距离压缩列表的起始地址有多少字节:
 *          通过这个偏移量，程序无须遍历整个压缩列表就可以确定表尾节点的地址
 *  zllen：  类型-uint16_t  长度 2字节
 *      记录了压缩列表包含的节点数量:当这个属性的值小于UINT16_MAX(65535)时，
 *      这个属性的值就是压缩列表包含节点的数量;
 *      当这个值等于UINT16_MAX时,节点的真实数量需要遍历整个压缩列表才能计算得出
 *  entry1：  列表节点    不定
 *      压缩列表包含的各个节点，节点的长度由节点保存的内容决定
 *       prevlen 记录了前一个节点的长度;
 *           ●如果前一个节点的长度小于254字节，那么prevlen 属性需要用1字节的空
 *              间来保存这个长度值;
 *           ●如果前一个节点的长度大于等于254字节，那么prevlen属性需要用5字节
 *              的空间来保存这个长度值;
 *       encoding 记录了当前节点实际数据的类型以及长度
 *       data 记录了当前节点的实际数据
 *
 *          entry节点主要内容：
 *            // prevrawlensize: 上一个节点的长度所占的字节数
 *            // prevrawlen: 上一个点的长度
 *            unsigned int prevrawlensize, prevrawlen;
 *
 *            // Lensize: 编码当前节点长度Len所需要的字节数
 *            // len:当前节点长度
 *            unsigned int Lensize, Len;
 *
 *            //当前节点的header大小，headersize = lensize + prevrawlensize
 *            unsigned int headersize;
 *
 *            //当前节点的编码格式
 *            unsigned char encoding ;
 *
 *            //当前节点指针
 *            unsigned char *p;
 *
 *  zlend：  类型-uint8_t  长度 1字节
 *      特殊值0xFF (十进制255)，用于标记压缩列表的末端
 *
 *
 * 明明有链表了，为什么出来一个压缩链表?
 * 1.普通的双向链表会有两个指针，在存储数据很小的情况下，我们存储的实际数据的大小可能还没有指针占用的内存大，得不偿失。
 *   ziplist 是一个特殊的双向链表没有维护双向指针:previous next;
 *   而是存储上一个entry的长度和当前entry的长度，通过长度推算下一个元素在什么地方。
 *   牺牲读取的性能，获得高效的存储空间，因为(简短字符串的情况)存储指针比存储entry长度更费内存。这是典型的“时间换空间”。
 * 2.链表在内存中一般是不连续的，遍历相对比较慢而ziplist可以很好的解决这个问题，
 *   普通数组的遍历是根据数组里存储的数据类型找到下一个元素的
 *   (例如int类型的数组访问下一个元素时每次只需要移动一个sizeof(int)就行)，
 *   但是ziplist的每 个节点的长度是可以不一样的， 而我们面对不同长度的节点又不可能直接sizeof(entry),
 *   所以ziplist只好将一些必 要的偏移量信息记录在了每一个节点里， 使之能跳到上一个节点或下一个 节点。
 *   备注:sizeof实际上是获取了数据在内存中所占用的存储空间，以字节为单位来计数。
 * 3.头节点里有头节点里同时还有一个参数len,和string类型提到的SDS类似，这里是用来记录链表长度的。
 *   因此获取链表长度时不用再遍历整个链表，直接拿到len值就可以了，这个时间复杂度是0(1)
 *
 * ziplist为了节省内存，采用了紧凑的连续存储。
 * ziplist是一个双向链表，可以在时间复杂度为O(1)下从头部、尾部进行pop或push。
 *      新增或更新元素可能会出现连锁更新现象(致命缺点导致被listpack替换)。<-------------*********
 * 不能保存过多的元素，否则查询效率就会降低，数量小和内容小的情况下可以使用。
 * @see B8_紧凑列表listpack
 * 两者各有优缺点:
 *      ●ziplist 的优点是内存紧凑，访问效率高,缺点是更新效率低，
 *      并且数据量较大时，可能导致大量的内存复制
 *      ●linkedlist的优点是节点修改的效率高，但是需要额外的内存开销，
 *      并且节点较多时，会产生大量的内存碎片
 *
 *
 */
public class B3_压缩列表ziplist {

}
